#include "Tessellator.h"
#include "Tessellation2.h"
#include "Polygon2.h"
#include "Contour2.h"
#include <iostream>
#include <algorithm>
#include <GL/glut.h>

// Helper struct passed as user data to gluTess
struct Tessellator::Job {
	Tessellator& tess;
	Polygon2& poly;
	std::vector<Tessellation2::Primitive> interior;
	Job( Tessellator& tess, Polygon2& poly ) : tess(tess), poly(poly) { }
};

// Implementation
class Tessellator::Client {
public:
	static void CALLBACK tessBeginPolygon( GLenum type, void* user ) {
		Tessellator::Job& j = *(Tessellator::Job*)user;
		j.interior.push_back( Tessellation2::Primitive( type ) );
	}

	static void CALLBACK tessVertex( void* data, void* user ) {
		Tessellator::Job& j = *(Tessellator::Job*)user;
		j.interior.back().vertices.push_back( *(Point2*)data );
	}

	static void CALLBACK tessCombine( GLdouble coords[3], void*, GLdouble[4], void** outData, void* user ) {
		Tessellator::Job& j = *(Tessellator::Job*)user;
		j.tess.mInputVertices.push_back( Point2( coords[0], coords[1] ) );
		*outData = &j.tess.mInputVertices.back();
	}

	static void CALLBACK tessError( GLenum err, void* user ) {
		std::clog << "gluTess error @" << user << ": " << gluErrorString( err ) << '\n';
	}
};

Tessellator::Tessellator()
	: mTess( gluNewTess() )
{
	typedef void (CALLBACK* tessCallback)();
	gluTessCallback( mTess, GLU_TESS_BEGIN_DATA, (tessCallback)Client::tessBeginPolygon );
	gluTessCallback( mTess, GLU_TESS_VERTEX_DATA, (tessCallback)Client::tessVertex );
	gluTessCallback( mTess, GLU_TESS_COMBINE_DATA, (tessCallback)Client::tessCombine );
	gluTessCallback( mTess, GLU_TESS_ERROR_DATA, (tessCallback)Client::tessError );
	gluTessNormal( mTess, 0, 0, 1 );
}

Tessellator::~Tessellator() {
	gluDeleteTess( mTess );
}


void Tessellator::tessellate( Polygon2& p ) {
	auto emitContour = [&] ( const Contour2& contour ) {
		beginContour(); {
			std::for_each( contour.vertices().begin(), contour.vertices().end(), [&](const Point2& vec){ vertex(vec); } );
		} endContour();
	};

	Job j( *this, p );
	beginPolygon( j ); {
		std::for_each( p.contours().begin(), p.contours().end(), emitContour );
	} endPolygon();

	mInputVertices.clear();
	std::sort( j.interior.begin(), j.interior.end() );
	p.mInterior.swap( j.interior );
}

// gluTess-related functions
void Tessellator::beginPolygon( Tessellator::Job& j ) {
	gluTessBeginPolygon( mTess, &j );
}

void Tessellator::endPolygon() {
	gluTessEndPolygon( mTess );
}

void Tessellator::beginContour() {
	gluTessBeginContour( mTess );
}

void Tessellator::endContour() {
	gluTessEndContour( mTess );
}

void Tessellator::vertex( const Point2& vec ) {
	GLdouble coord[3] = { vec.x, vec.y, 0. };
	mInputVertices.push_back( vec );
	gluTessVertex( mTess, coord, &mInputVertices.back() );
}